package main

import (
	"compress/gzip"
	"fmt"
	"net/http"
	"net/http/httputil"
	"net/url"
	"regexp"
	"strings"
	"time"
)

// PathRewriteRule 表示重写路径的一条规则
type PathRewriteRule struct {
	Test        *regexp.Regexp
	Replacement string
}

// 初始化代理处理程序。
func initProxies(mux *http.ServeMux) error {
	for proxyEntry, proxyConfig := range configuration.Proxies {
		target, err := url.Parse(proxyConfig.Target)
		if err != nil {
			return err
		}

		if !target.IsAbs() {
			return fmt.Errorf("illegal target of proxy: %q, not absolute", proxyConfig.Target)
		}

		if target.Scheme != "http" && target.Scheme != "https" {
			// 未解析得到正确的Scheme
			return fmt.Errorf("illegal target of proxy: %q, missing http or https?", proxyConfig.Target)
		}

		proxyEntry = strings.TrimSpace(proxyEntry)
		if !strings.HasPrefix(proxyEntry, "/") {
			proxyEntry = "/" + proxyEntry
		}
		if !strings.HasSuffix(proxyEntry, "/") {
			proxyEntry = proxyEntry + "/"
		}

		pathRewriteRules := make([]*PathRewriteRule, 0, len(proxyConfig.PathRewriteRule))
		for pathTest, pathReplacement := range proxyConfig.PathRewriteRule {
			pathTestRegexp, err := regexp.Compile(pathTest)
			if err == nil {
				pathRewriteRules = append(pathRewriteRules, &PathRewriteRule{Test: pathTestRegexp, Replacement: pathReplacement})
			} else {
				return err
			}
		}

		// TODO: 通过 Transport 字段设置backend的请求超时时间。
		reverseProxy := &httputil.ReverseProxy{
			Director: func(r *http.Request) {
				doDirect(r, target, pathRewriteRules)
			},

			FlushInterval: 2 * time.Second,
		}

		mux.HandleFunc(proxyEntry, func(w http.ResponseWriter, r *http.Request) {
			reverseProxy.ServeHTTP(&gzWriter{w: w, level: gzip.BestCompression}, r)
		})

		fmt.Printf("Create proxy: %s => %s\n", proxyEntry, target)
	}

	return nil
}

func doDirect(r *http.Request, target *url.URL, pathRewriteRules []*PathRewriteRule) {
	originRequestURI := r.URL.RequestURI()

	if containsDotDot(originRequestURI) {
		panic(fmt.Errorf("illegal origin request uri: %q", originRequestURI))
	}

	if newRequestURI, err := url.Parse(rewritePath(r.URL.Path, pathRewriteRules)); err != nil {
		panic(err)
	} else {
		if rq := r.URL.RawQuery; rq != "" {
			newRequestURI.RawQuery = rq
		}
		r.URL = target.ResolveReference(newRequestURI)
	}

	httpLog("(Proxy) "+r.Method, originRequestURI, r.URL.String(), 0, nil)
}

func rewritePath(s string, pathRewriteRules []*PathRewriteRule) string {
	for _, rule := range pathRewriteRules {
		s = rule.Test.ReplaceAllLiteralString(s, rule.Replacement)
	}

	if strings.HasPrefix(s, "/") {
		return s[1:]
	}

	return s
}
